package com.csun.cmny.provider.device.csun.camera;

import com.csun.cmny.provider.controller.websocket.WebSocketUtils;
import com.csun.cmny.provider.device.csun.camera.entity.CsunCameraConstant;
import com.csun.cmny.provider.device.csun.camera.entity.CsunCameraData;
import com.csun.cmny.provider.model.constant.LifeConstant;
import com.csun.cmny.provider.model.domain.DmcDeviceAlert;
import com.csun.cmny.provider.model.dto.device.DeviceStatus;
import com.csun.cmny.provider.model.enums.device.DeviceStatusEnum;
import com.csun.cmny.provider.model.vo.OldmanBedRoomBuildingVo;
import com.csun.cmny.provider.service.DmcDeviceAlertService;
import com.csun.cmny.provider.service.DmcDeviceOnlineRecordService;
import com.csun.cmny.util.PublicUtil;
import com.csun.cmny.util.RedisKeyUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Auther: chisj chisj@foxmal.com
 * @Date: 2019-12-27 08:57
 * @Description:
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class CameraNettyTcpServerHandler extends ChannelInboundHandlerAdapter {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private CameraMessageHandler cameraMessageHandler;
    @Resource
    private DmcDeviceAlertService dmcDeviceAlertService;
    @Resource
    private DmcDeviceOnlineRecordService dmcDeviceOnlineRecordService;
    @Resource
    private WebSocketUtils webSocketUtils;

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info("# camera client registered...：   [{}] ...", ctx.channel());
    };

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            ByteBuf in = (ByteBuf) msg;
            String receiveMsg = in.toString(CharsetUtil.UTF_8);
            log.info("=====>    camera channel read msg: {}", receiveMsg);
            cameraMessageHandler.handler(ctx, receiveMsg);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("=====>  camera channel read error: {}", e.getMessage());
        } finally {
            // 抛弃收到的数据
            ReferenceCountUtil.release(msg);
        }
    }

    /**
     * 摄像头链接断开
     *
     * @param ctx
     * @throws Exception
     */
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        String deviceNo = "not know device no.";

        CsunCameraData csunCameraData = ctx.channel().attr(CsunCameraConstant.CAMERA_CHANNEL_KEY).get();
        if (PublicUtil.isNotEmpty(csunCameraData) && PublicUtil.isNotEmpty(csunCameraData.getDeviceNo())) {
            deviceNo = csunCameraData.getDeviceNo();
        }

        log.info("camera netty tcp channel inactive... {}, deviceNo = {}", ctx.toString(), deviceNo);

        // dealCameraOff(ctx);
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        String deviceNo = "not know device no.";

        CsunCameraData csunCameraData = ctx.channel().attr(CsunCameraConstant.CAMERA_CHANNEL_KEY).get();
        if (PublicUtil.isNotEmpty(csunCameraData) && PublicUtil.isNotEmpty(csunCameraData.getDeviceNo())) {
            deviceNo = csunCameraData.getDeviceNo();
        }

        log.info("camera netty tcp exception caught... {}, deviceNo = {}, cause = {}", ctx.toString(), deviceNo, cause.getMessage());

        // dealCameraOff(ctx);
    }

    public void closeConnection(ChannelHandlerContext ctx) {

        log.info("camera netty tcp close... {}, ", ctx.toString());

        // ctx.close();
    }

    /**
     * 这里是保持服务器与客户端长连接  进行心跳检测 避免连接断开
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            PingWebSocketFrame ping = new PingWebSocketFrame();
            switch (stateEvent.state()){
                //读空闲（服务器端）
                case READER_IDLE:
                    log.info("===> camera netty tcp【"+ctx.channel().remoteAddress()+"】读空闲（服务器端）");
//                    ctx.writeAndFlush(ping);
//                    dealCameraOff(ctx);
                    break;
                //写空闲（客户端）
                case WRITER_IDLE:
                    log.info("===> camera netty tcp【"+ctx.channel().remoteAddress()+"】写空闲（客户端）");
//                    ctx.writeAndFlush(ping);
                    break;
                case ALL_IDLE:
                    log.info("===> camera netty tcp【"+ctx.channel().remoteAddress()+"】读写空闲");
                    break;
            }
        }
    }

    public void dealCameraOff(ChannelHandlerContext ctx) {
        log.info("=====> deal camera off [{}]", ctx.channel());
        try {
            CsunCameraData csunCameraData = ctx.channel().attr(CsunCameraConstant.CAMERA_CHANNEL_KEY).get();
            if (PublicUtil.isNotEmpty(csunCameraData)) {
                String deviceNo = csunCameraData.getDeviceNo();
                String redisKey = RedisKeyUtil.getCsunCameraKey(deviceNo);
                csunCameraData = (CsunCameraData) redisTemplate.opsForValue().get(redisKey);
                /**
                 * @data    2020-03-17 18:04
                 * @author  chisj
                 * @desc    解决摄像头离线有两条离线记录
                 */
                if (PublicUtil.isNotEmpty(csunCameraData) &&
                        DeviceStatusEnum.ONLINE.getKey().equals(csunCameraData.getStatus())) {

                    log.info("=====> camera [{}] is off", deviceNo);
                    csunCameraData.setStatus(DeviceStatusEnum.OFFLINE.getKey());

                    redisTemplate.opsForValue().set(redisKey, csunCameraData);

                    // 更新数据
                    // watchMessageHandler.updateWatchData(watchDataEntity);

                    /*
                    * 2020-03-11 xcr
                    * 加入报警的离线推送判断
                    * */
                    DmcDeviceAlert dmcDeviceAlert = new DmcDeviceAlert();
                    OldmanBedRoomBuildingVo obrvo = new OldmanBedRoomBuildingVo();
                    int i = dmcDeviceAlertService.deviceOfflineAlert(deviceNo, dmcDeviceAlert, obrvo);
                    if(i > 0){
                        dmcDeviceAlertService.sendDeviceAlertMsg(dmcDeviceAlert, obrvo);
                        dmcDeviceAlertService.sendLifeMsg(dmcDeviceAlert, obrvo, LifeConstant.YES_SOUND);
                    }
                    // 推送设备状态改变消息
                    DeviceStatus deviceStatus = new DeviceStatus();
                    deviceStatus.setDeviceId(csunCameraData.getDeviceId());
                    deviceStatus.setGroupId(csunCameraData.getGroupId());
                    deviceStatus.setDeviceNo(csunCameraData.getDeviceNo());
                    deviceStatus.setMessage(String.format("设备%s离线", csunCameraData.getDeviceNo()));
                    webSocketUtils.pushWebsocketMessageWithStatus(deviceStatus);

                } else {
                    log.info("=====> camera {} is be break.", deviceNo);
                }
            } else {
                log.info("=====> camera channel not login.");
            }

            // 端开连接
            ctx.close();

            // 清除
            CsunCameraConstant.CAMERA_CHANNEL_MAP.remove(csunCameraData.getDeviceNo());
        } catch (Exception e) {
            log.error("=====> deal camera off error: ", e.getMessage());
        }

    }
}
